package com.boss.redis.util;

import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * 针对redisson分布式锁进一步封装
 *
 * @author 夏柯(xiake @ bosssoft.com.cn)
 * @date 2021/11/29 16:40
 */
@Slf4j
@Component
public class RedissonUtil implements ApplicationContextAware {

    /**
     * 连接等待时长
     */
    private static Integer WAIT_TIME = 100;

    private static RedissonClient redissonClient;

    private final static String GET_ERROR_NAME = "获取锁名称失败";

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RedissonUtil.redissonClient = applicationContext.getBean(RedissonClient.class);
    }

    /**
     * 获取锁名称
     *
     * @param rLock
     * @return
     */
    public static String getLockName(RLock rLock) {
        try {
            return rLock.getName();
        } catch (Exception e) {
            //部分锁的实现，无法获取name
            return GET_ERROR_NAME;
        }
    }

    public static <T, E extends Exception> T tryLock(RLock rLock, long lockTime, Supplier<T> lockSupplier, Supplier<E> exceptionSupplier, Consumer<RLock> closeLockConsumer) {
        return tryLock(rLock, TimeUnit.MINUTES.toSeconds(lockTime), TimeUnit.SECONDS, lockSupplier, exceptionSupplier, closeLockConsumer);
    }

    public static <T, E extends Exception> T tryLock(RLock rLock, long lockTime, TimeUnit timeUnit, Supplier<T> lockSupplier, Supplier<E> exceptionSupplier, Consumer<RLock> closeLockConsumer) {
        return tryLock(rLock, 10L, lockTime, timeUnit, lockSupplier, exceptionSupplier, closeLockConsumer);
    }

    /**
     * @param rLock             redisson锁对象
     * @param waitTime          等待加锁的时间
     * @param lockTime          加锁时间，单位是分钟
     * @param timeUnit          时间单位
     * @param lockSupplier      加锁成功要执行的代码块
     * @param exceptionSupplier 加锁失败要抛出的异常代码块
     * @param closeLockConsumer 关锁代码块
     * @return 执行成功返回的结果，如果加锁失败，并且不抛出异常，则返回null
     */
    public static <T, E extends Exception> T tryLock(RLock rLock, long waitTime, long lockTime, TimeUnit timeUnit, Supplier<T> lockSupplier, Supplier<E> exceptionSupplier, Consumer<RLock> closeLockConsumer) {
        try {
            if (rLock.tryLock(waitTime, lockTime, timeUnit)) {
                return lockSupplier.get();
            }
            //到这里就说明加锁失败了
            if (exceptionSupplier != null) {
                throw exceptionSupplier.get();
            }
            return null;
        } catch (Exception e) {
            log.error("加锁代码块发生异常，当前lockName=>[{}]", getLockName(rLock), e);
            throw new RuntimeException(e);
        } finally {
            closeLockConsumer.accept(rLock);
        }
    }

    /**
     * @param rLock             redisson锁对象
     * @param lockTime          加锁时间，单位是分钟
     * @param lockSupplier      加锁成功要执行的代码块
     * @param exceptionSupplier 加锁失败要抛出的异常代码块
     * @return 执行成功返回的结果，如果加锁失败，并且不抛出异常，则返回null
     */
    public static <T, E extends Exception> T tryLock(RLock rLock, long lockTime, Supplier<T> lockSupplier, Supplier<E> exceptionSupplier) {
        return tryLock(rLock, lockTime, lockSupplier, exceptionSupplier, lock -> {
            if (rLock != null && rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        });
    }

    /**
     * 可重入锁，加锁成功期间可以重复获取锁
     */
    public static class ReentrantLock {

        /**
         * 加锁方法，有返回值
         */
        public static <T, E extends Exception> T tryLock(String lockName, long lockTime, Supplier<T> lockSupplier, Supplier<E> exceptionSupplier) {
            RLock rLock = redissonClient.getLock(lockName);
            return RedissonUtil.tryLock(rLock, lockTime, lockSupplier, exceptionSupplier);
        }

        /**
         * 加锁方法，无返回值
         */
        public static void tryLock(String lockName, long lockTime, Runnable runnable, Supplier<? extends Exception> exceptionSupplier) {
            tryLock(lockName, lockTime, () -> {
                runnable.run();
                return 1;
            }, exceptionSupplier);
        }

        /**
         * 加锁方法，有返回值，加锁失败不抛出异常
         */
        public static <T> T tryLock(String lockName, long lockTime, Supplier<T> lockSupplier) {
            return tryLock(lockName, lockTime, lockSupplier, null);
        }

        /**
         * 加锁方法，无返回值，加锁失败不抛出异常
         */
        public static void tryLock(String lockName, long lockTime, Runnable runnable) {
            tryLock(lockName, lockTime, () -> {
                runnable.run();
                return 1;
            }, null);
        }

    }

    /**
     * 公平锁，将尝试获取锁的线程放到先进先出的队列，队列有序
     */
    public static class FairLock {

        public static <T, E extends Exception> T tryLock(String lockName, long lockTime, Supplier<T> lockSupplier, Supplier<E> exceptionSupplier) {
            RLock fairLock = redissonClient.getFairLock(lockName);
            return RedissonUtil.tryLock(fairLock, lockTime, lockSupplier, exceptionSupplier);
        }

        public static <E extends Exception> void tryLock(String lockName, long lockTime, Runnable runnable, Supplier<E> exceptionSupplier) {
            tryLock(lockName, lockTime, () -> {
                runnable.run();
                return 1;
            }, exceptionSupplier);
        }

        public static <T> T tryLock(String lockName, long lockTime, Supplier<T> lockSupplier) {
            return tryLock(lockName, lockTime, lockSupplier, null);
        }

        public static void tryLock(String lockName, long lockTime, Runnable runnable) {
            tryLock(lockName, lockTime, () -> {
                runnable.run();
                return 1;
            }, null);
        }

    }

    /**
     * 联锁，RLock对象关联为一个联锁，所有的锁对象加锁成功后，表示联锁加锁成功
     */
    public static class MultiLock {

        public static <T, E extends Exception> T tryLock(List<String> lockNameList, long lockTime, Supplier<T> lockSupplier, Supplier<E> exceptionSupplier) {
            Assert.notEmpty(lockNameList, () -> new RuntimeException("锁名称不能为null"));

            RLock[] rLockArray = lockNameList.stream().map(lockName -> redissonClient.getLock(lockName)).toArray(RLock[]::new);
            RLock multiLock = redissonClient.getMultiLock(rLockArray);
            return RedissonUtil.tryLock(multiLock, lockTime, lockSupplier, exceptionSupplier, rLock -> {
                if (rLock != null && rLockArray[0].isHeldByCurrentThread()) {
                    rLock.unlock();
                }
            });
        }

        public static <E extends Exception> void tryLock(List<String> lockNameList, long lockTime, Runnable runnable, Supplier<E> exceptionSupplier) {
            tryLock(lockNameList, lockTime, () -> {
                runnable.run();
                return 1;
            }, exceptionSupplier);
        }

        public static <T> T tryLock(List<String> lockNameList, long lockTime, Supplier<T> lockSupplier) {
            return tryLock(lockNameList, lockTime, lockSupplier, null);
        }

        public static void tryLock(List<String> lockNameList, long lockTime, Runnable runnable) {
            tryLock(lockNameList, lockTime, () -> {
                runnable.run();
                return 1;
            }, null);
        }

    }

    /**
     * 加锁操作
     *
     * @return true = 加锁成功 false=加锁失败
     */
    public boolean lock(String lockName, int expireSeconds) {
        RLock rLock = redissonClient.getLock(lockName);
        boolean getLock = false;
        try {
            getLock = rLock.tryLock(WAIT_TIME, expireSeconds, TimeUnit.SECONDS);
            if (getLock) {
                log.debug("获取Redisson分布式锁[成功],lockName={}", lockName);
            } else {
                log.debug("获取Redisson分布式锁[失败],lockName={}", lockName);
            }
        } catch (InterruptedException e) {
            log.error("获取Redisson分布式锁[异常]，lockName=" + lockName, e);
            Thread.currentThread().interrupt();
        }
        return getLock;
    }

    /**
     * 解锁
     *
     * @param lockName 锁名
     */
    public void unlock(String lockName) {
        RLock lock = redissonClient.getLock(lockName);
        if (lock.isLocked()) {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.debug("释放Redisson分布式锁[成功],lockName={}");
            } else {
                log.debug("当前线程不持有Redisson分布式锁,本次不执行解锁操作,lockName={}", lockName);
            }
        } else {
            log.debug("获取Redisson分布式锁[不存在],本次不执行解锁操作,lockName={}", lockName);
        }
    }
    //    *********** 全局互锁操作  结束 ***********

}
